package edu.uwm.cs552;

import java.awt.Color;
import java.awt.Graphics;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import edu.uwm.cs552.gfx.SVGImageCreator;
import edu.uwm.cs552.util.FormatException;

/**
 * The fixed part of a hexagonal game board.
 * We have a back image and terrain for certain coordinates.
 * <em>We don't handle rivers or other boundaries yet.</em>
 */
public class HexBoard extends HashMap<HexCoordinate,Terrain> {
	/**
	 * We do not need to serialize this class, we just need to keep eclipse happy
	 */
	private static final long serialVersionUID = 1L;
	
	private SVGImageCreator background;
	private double naturalWidth, naturalHeight;
	private double TWOSQRT33 = 2.0 * Math.sqrt(3.0) / 3.0;
	/**
	 * Create a hex board with the given background image
	 * and natural width and height (in terms of hexagon widths).
	 * @param im background image, may be null
	 * @param w width
	 * @param h height
	 */
	public HexBoard(SVGImageCreator im, double w, double h) {
		background = im;
		naturalWidth = w;
		naturalHeight = h;
		
		int rowOffset = 0;
		for(int i = 0; i < naturalHeight * TWOSQRT33; ++i) {
			for(int j = rowOffset; j < naturalWidth + rowOffset; j++) {
				this.put(new HexCoordinate(j,i), null);
			}
			
			if (i % 2 == 0)
				++rowOffset;
		}
	}
	
	
	/**
	 * Draw the game board into the given graphics context.
	 * @param g graphics context, must not be null
	 * @param scale width of hexagons
	 * @param drawMesh whether to draw the outline of all hexagons
	 */
	public void draw(Graphics g, double scale, boolean drawMesh) {
		if (this.background != null)
			this.background.draw(g, (int)Math.round(scale * naturalWidth), (int)Math.round(scale * naturalHeight));
		Iterator<Entry<HexCoordinate, Terrain>> it = this.entrySet().iterator();
		g.setColor(Color.BLACK);
		while (it.hasNext()) {
			Entry<HexCoordinate, Terrain> entry = it.next();
			if (entry.getValue() != null)
				entry.getValue().draw(g, entry.getKey(), scale);
			if (drawMesh) {
				g.drawPolygon(entry.getKey().toPolygon(scale));
			}
		}
	}

	/**
	 * Get the height of the board (the height of the background image)
	 * given the current scale.
	 * @param scale scale to use
	 * @return height of board.
	 */
	public int getHeight(double scale) {
		return (int)(naturalHeight*scale);
	}

	/**
	 * Get the width of the board (the width of the background image)
	 * given the current scale.
	 * @param scale scale to use
	 * @return width of board
	 */
	public int getWidth(double scale) {
		return (int)(naturalWidth*scale);
	}

	/**
	 * Write a hexboard out using the form:
	 * <pre>
	 * <i>URL (or empty, if no background image)</i>
	 * <i>width</i>
	 * <i>height</i>
	 * <i>[The following section repeated indefinitely]</i>
	 * <i>terrain</i>
	 * <i>hexcoordinate<sb>1</db></i>
	 * <i>hexcoordinate<sb>2</db></i>
	 * ...
	 * <i>hexcoordinate<sb>n</db></i>
	 * </pre>
	 * @param pw output to use, must not be null
	 */
	public void write(PrintWriter pw) throws IllegalArgumentException {
		if(pw == null) {throw new IllegalArgumentException();}
		
		pw.println(background.getURL().toString());
		pw.println(naturalWidth);
		pw.println(naturalHeight);
		Terrain t = null;
		Iterator<Entry<HexCoordinate, Terrain>> it = this.entrySet().iterator();
		while(it.hasNext()) {
			Map.Entry<HexCoordinate, Terrain> pairs = (Map.Entry<HexCoordinate, Terrain>) it.next();
			if (pairs.getValue() != null) {
				if(t == null || t != pairs.getValue()) {
					pw.println(pairs.getValue().toString());
					t = pairs.getValue();
				}
				pw.println(pairs.getKey().toString());
			}
		}
	}
	
	/**
	 * Read in a hex board using the format given in {@link #write(PrintWriter)}.
	 * Throw an appropriate exception given a problem.
	 * @param br buffered reader to use, must not be null
	 * @return a new hex board
	 * @throws IOException if there is an error reading the file, or if the URL is bad, or the SVG in it
	 * @throws FormatException an error reading a hex coordinate or terrain
	 * @throws NumberFormatException an error reading the width or height
	 */
	public static HexBoard read(BufferedReader br) throws IOException, FormatException, NumberFormatException {
		String line = null;
		URL svgUrl = null;
		double width = -1.0d;
		double height = -1.0d;
		Terrain curTerrain = null;
		HexCoordinate hexCoordinate = null;
		Map<HexCoordinate, Terrain> read = new HashMap<HexCoordinate, Terrain>();
		
	    while (true) {
	    	line = null;
	    	try {
				line = br.readLine();
			} catch (Exception e1) {
				throw new IOException(e1);
			}
	    	if (line == null) break;
	    	if (svgUrl == null) {
				try {
					svgUrl = new URL(line);
				} catch (Exception e) {
					throw new FormatException(e);
				}
	    	}
	    	else if (width == -1) {
	    		try {
					width = Double.parseDouble(line);
				} catch (NumberFormatException e) {
					throw e;
				}
	    		if (width <= 0.0d)
					throw new FormatException("Width must be greater than 0.0");
	    	}
	    	else if (height == -1) {
	    		try {
	    			height = Double.parseDouble(line);
				} catch (NumberFormatException e) {
					throw e;
				}
	    		if (height <= 0.0d)
					throw new FormatException("Height must be greater than 0.0");
	    	}
	    	else if (!line.startsWith("<")) {
	    		try {
					curTerrain = Terrain.valueOf(line);
				} catch (Exception e) {
					throw new FormatException(e);
				}
	    	}
	    	else {
	    		try {
					hexCoordinate = HexCoordinate.fromString(line);
				} catch (Exception e) {
					throw new FormatException(e);
				}
	    		read.put(hexCoordinate, curTerrain);
	    	}
	    }
	    
	    HexBoard hb = new HexBoard(new SVGImageCreator(svgUrl), width, height);
	    hb.putAll(read);
	    return hb;
	}
}
